#region File Header
//
// CRC16.cs - Computes a 16-bit CRC 
//
// Copyright (C) Javier Valcarce. BSD License
#endregion

#region Using Statements
using System;
using System.Diagnostics;
using System.IO;
using System.Collections.Generic;
using System.Text;
#endregion

namespace Arq
{
    /// <summary>
    /// Cyclic code redundancy calculator for any 16-bit generator polynomial. The conventions 
    /// followed to calculate it are those used commonly in hardware (which are diferent from 
    /// the conventions followed in software)
    /// 
    /// Convention: Remaindet initialized with InitValue = 0xFFFF
    /// Convention: The first serial data bit entering in the LFSR is the byte's MSB
    /// Convention: Polynomial x^16 + x^15 + x^2 + 1 is represented as 0x8005 (not 0xA001)
    /// </summary>
    public class Crc16 
    {
        ushort polinomial;
        ushort crc;
        int    count;
        static ushort[] table;
        public const ushort InitValue = 0xFFFF;
        public const ushort CRC_16 = 0x8005;



        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="polinomial">Generator polynomial in hexadecimal notation. For example: the 
        /// polinomial g(x) = 1 + x2 + x15 + x16 has a hexadecimal representation 0x8005</param>
        public Crc16(ushort polinomial)
        {
            Debug.Assert((polinomial & 0x0001) == 0x0001, 
                "Bad polynomial, zero order coeficcient must be 1"); 

            this.polinomial = polinomial;
            // Initialized with InitValue
            crc = InitValue;
            count = 0;

            // table
            table = new ushort[0x10000]; // 0xFFFF + 1

            ushort t;
            ushort c;

            for (int r = 0; r < 0x10000; r++)            
            {
                c = (ushort) r;

                for (int j = 0; j < 8; j++)
                {
                    t = (ushort)(c & 0x8000);
                    c = (ushort)(c << 1);

                    if (t == 0x8000)
                    {
                        c = (ushort)(c ^ polinomial);
                    }
                }

                table[r] = c;
            }

            //for (int r = 0; r < 0x10000; r++)
            //{
            //    Console.WriteLine("{0,5} -> 0x{1:x4}", r, crctable[r]);
            //}
        }

        #region Public Methods
        /// <summary>
        /// Feed the LFSR (Linear Feedback Shift Register) with bytes buf[offset] to 
        /// buf[offset + count - 1] and update the CRC
        /// </summary>
        /// <param name="buf">The array</param>
        /// <param name="offset">Offset in the array</param>
        /// <param name="count">Number of bytes computed</param>
        public void Compute(byte[] buf, int offset, int count)
        {
            ushort s;
           
            for (int i = 0; i < count; i++)
            {
                s   = (ushort)(buf[offset + i] << 8);
                crc = (ushort)(crc ^ s);

                crc = table[crc];
            }
            this.count += count;
        }

        /// <summary>
        /// Feed the LFSR (Linear Feedback Shift Register) with the byte b and update the CRC
        /// </summary>
        /// <param name="b">Byte feeded into the LFSR</param>
        public void Compute(byte b)
        {
            ushort s;

            s   = (ushort)(b  << 8);
            crc = (ushort)(crc ^ s);

            crc = table[crc];

            this.count++;
        }

        /// <summary>
        /// Computes a 16-bit CRC from frame[offset + 0] to frame[offset + count - 3] and store it 
        /// at the last 2 bytes, frame[offset + count - 2] and frame[offset + count - 1] in 
        /// little-endian or big-endian depending on bigEndian parameter.
        /// </summary>
        /// <param name="frame"></param>
        /// <param name="offset"></param>
        /// <param name="count"></param>
        /// <param name="bigEndian">True to use big-endian, false for little-endian</param>
        public void AppendCode(byte[] frame, int offset, int count, bool bigEndian)
        {
            ushort localcrc = InitValue;
            ushort s;

            for (int i = 0; i < count - 2; i++)
            {
                s        = (ushort)(frame[offset + i] << 8);
                localcrc = (ushort)(localcrc           ^ s);
                localcrc = table[localcrc];
            }

            byte lsb = (byte)((localcrc & 0x00FF) >> 0);
            byte msb = (byte)((localcrc & 0xFF00) >> 8);

            if (bigEndian)
            {
                frame[offset + count - 2] = msb;
                frame[offset + count - 1] = lsb;
            }
            else
            {
                frame[offset + count - 2] = lsb;
                frame[offset + count - 1] = msb;
            }
        }

        /// <summary>
        /// Check a frame. Computes CRC from frame[0] to frame[frame.Length-3]. It is supposed that 
        /// then redundancy is in the last 2 bytes of the array frame[frame.Length - 2] and 
        /// frame[frame.Length - 1].
        /// </summary>
        /// <param name="frame">Frame to be checked</param>
        /// <param name="offset"></param>
        /// <param name="count"></param>
        /// <param name="bigEndian">True to use big-endian, false for little-endian</param>
        /// <returns>True if valid frame, false if not</returns>
        public bool ValidFrame(byte[] frame, int offset, int count, bool bigEndian)
        {
            ushort localcrc = InitValue;
            ushort s;

            for (int i = 0; i < count - 2; i++)
            {
                s        = (ushort)(frame[offset + i] << 8);
                localcrc = (ushort)(localcrc ^ s);
                localcrc = table[localcrc];
            }

            byte lsb = (byte)((localcrc & 0x00FF) >> 0);
            byte msb = (byte)((localcrc & 0xFF00) >> 8);

            if (bigEndian)
            {
                if (frame[offset + count - 2] != msb) return false;
                if (frame[offset + count - 1] != lsb) return false;
            }
            else
            {
                if (frame[offset + count - 2] != lsb) return false;
                if (frame[offset + count - 1] != msb) return false;
            }

            return true;
        }

        /// <summary>
        /// Resets current CRC value
        /// </summary>
        public void Reset()
        {
            crc = InitValue;
            count = 0;
        }
        #endregion

        #region Properties
        /// <summary>
        /// Number of bytes computed
        /// </summary>
        public int    Count { get { return count; } }

        /// <summary>
        /// Current CRC (16-bits) computed value
        /// </summary>
        public ushort Code  { get { return crc;   } }

        #endregion

    }
}
